package at.ac.tuwien.lsds.model;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Observable;
import java.util.Random;

import org.apache.log4j.Logger;

import at.ac.tuwien.lsds.exception.MachineException;

public class PhysicalMachineImpl extends MachineImpl implements PhysicalMachine{
	private static final Logger log = Logger.getLogger(PhysicalMachineImpl.class);
	
	//vm init values
	private Integer initVmHdd = 100;
	private Integer initVmRam = 50;
	private Integer initVmCpu = 150;
	
	//pm init values
	private Integer initPmHdd = 850;
	private Integer initPmRam = 300;
	private Integer initPmCpu = 500;

	private Float wToMhz = 0.3f;
	private Float defaultPowerConsumption = 200f;
	private volatile List<VirtualMachine> vms;
	
	public PhysicalMachineImpl(){
		hdd = 50000;
		ram = 4700;
		cpu = 2400;
		
		hddInUse = initPmHdd;
		ramInUse = initPmRam;
		cpuInUse = initPmCpu;
		
		vms = new ArrayList<VirtualMachine>();
	}
	
	@Override
	public void addApplication(Application app) throws MachineException{		
		addApplication(app, true);
	}
	
	@Override
	public void addApplication(Application app, Boolean startImmediately) throws MachineException {
		Boolean hasAssinged = false;
		Iterator<VirtualMachine> iterator = vms.iterator();
		
		while(iterator.hasNext()){
			VirtualMachine vm = iterator.next();
			int vmFreeHdd = vm.getHdd() - vm.getHddInUse();
			int vmFreeRam = vm.getRam() - vm.getRamInUse();
			int vmFreeCpu = vm.getCpu() - vm.getCpuInUse();
			
			if(vmFreeHdd >= app.getHdd() && vmFreeRam >= app.getRam() && vmFreeCpu >= app.getCpu()){
				vm.addApplication(app, startImmediately);
				hasAssinged = true;
				break;
			}
		}
		
		if(!hasAssinged){
			log.info("Not enough Ressources");
			throw new MachineException("Not enough Ressources");
		}
	}
	
	@Override
	public synchronized void removeAllApplication() {
		for (VirtualMachine vm : vms) {
			vm.removeAllApplication();
		}
		
		// Clear lists
		vms = new ArrayList<VirtualMachine>(); 
		
		this.cpuInUse = this.initPmCpu + (this.initVmCpu * this.vms.size());
		this.ramInUse = this.initPmRam + (this.initVmRam * this.vms.size());
		this.hddInUse = this.initPmHdd + (this.initVmHdd * this.vms.size());
	}
	
	@Override
	public void shutdown() {
		for (VirtualMachine vm : vms) {
			vm.shutdown();
		}
	}
	
	@Override
	public void startup() {
		// Vermeide ConcurrentModificationException, weil das Starten des Threads
		// kann virtuelle Maschinen entfernen, daher ist eine for-Schleife nicht
		// das passende hier.
		int i = 0;
		
		while (i < vms.size()) {
			VirtualMachine vm = vms.get(i);

			vm.startup();

			i++;
		}
	}
	
	@Override
	public synchronized void update(Observable o, Object arg) {
		VirtualMachine vm = (VirtualMachine)o;
		
		int divHdd = vm.getHdd() - vm.getHddInUse();
		int divRam = vm.getRam() - vm.getRamInUse();
		int divCpu = vm.getCpu() - vm.getCpuInUse();
		
		if(vm.getHddInUse() >= initVmHdd){
			try {
				vm.setHdd(vm.getHddInUse());
				hddInUse -= divHdd;
			} catch (MachineException e) {
				log.error(e.getMessage());
			}
		}
		
		if(vm.getRamInUse() >= initVmRam){
			try {
				vm.setRam(vm.getRamInUse());
				ramInUse -= divRam;
			} catch (MachineException e) {
				log.error(e.getMessage());
			}
		}
		
		if(vm.getCpuInUse() >= initVmCpu){
			try {
				vm.setCpu(vm.getCpuInUse());
				cpuInUse -= divCpu;
			} catch (MachineException e) {
				log.error(e.getMessage());
			}
		}
		
		if(0 == vm.getRunningAppCount()){
			vm.shutdown();
			
			hddInUse -= vm.getHdd();
			ramInUse -= vm.getRam();
			cpuInUse -= vm.getCpu();
			
			vms.remove(vm);
			
			log.debug("Shutdown Virtual Machine - VM Count: " + vms.size());
		} else {
			log.debug("##Don't shutdown Virtual Machine - VM Count: " + vms.size());
		}
		
		setChanged();
	
	notifyObservers();
		  
		
	}
	
	@Override
	public void startVM(Integer hdd, Integer ram, Integer cpu) throws MachineException{
		int freeHdd = this.hdd - hddInUse;
		int freeRam = this.ram - ramInUse;
		int freeCpu = this.cpu - cpuInUse;
		
		if(freeHdd < (hdd + initVmHdd) || freeRam < (ram + initVmRam) || freeCpu < (cpu + initVmCpu) || shutdown){
			log.info("Not enough Ressources");
			throw new MachineException("Not enough Ressources");
		}
		
		VirtualMachine vm = new VirtualMachineImpl(hdd, ram, cpu);
		((VirtualMachineImpl)vm).addObserver(this);
		
		hddInUse += vm.getHdd();
		ramInUse += vm.getRam();
		cpuInUse += vm.getCpu();
		
		vms.add(vm);
	}
	
	@Override
	public void increaseVMResources(Integer hdd, Integer ram, Integer cpu) throws MachineException{
		int freeHdd = this.hdd - hddInUse;
		int freeRam = this.ram - ramInUse;
		int freeCpu = this.cpu - cpuInUse;
		
		if(freeHdd < hdd || freeRam < ram || freeCpu < cpu || shutdown){
			log.info("Not enough Ressources");
			throw new MachineException("Not enough Ressources");
		}
		
		Random rand = new Random();
		int randomNum = rand.nextInt(vms.size());
		VirtualMachine vm = vms.get(randomNum);
		
		vm.setHdd(vm.getHdd() + hdd);
		vm.setRam(vm.getRam() + ram);
		vm.setCpu(vm.getCpu() + cpu);
		
		hddInUse += hdd;
		ramInUse += ram;
		cpuInUse += cpu;
	}
	
	@Override
	public void setVMResources(Integer hdd, Integer ram, Integer cpu) throws MachineException {
		int freeHdd = this.hdd - hddInUse;
		int freeRam = this.ram - ramInUse;
		int freeCpu = this.cpu - cpuInUse;
		
		if(freeHdd < hdd || freeRam < ram || freeCpu < cpu || shutdown){
			log.info("Not enough Ressources");
			throw new MachineException("Not enough Ressources");
		}
		
		Random rand = new Random();
		int randomNum = rand.nextInt(vms.size());
		VirtualMachine vm = vms.get(randomNum);
		
		// 2013-05-21, Alex
		// Don't forget to calculate the initial resources
		vm.setHdd(this.initVmHdd + hdd);
		vm.setRam(this.initVmRam + ram);
		vm.setCpu(this.initVmCpu + cpu);
		
		hddInUse += hdd;
		ramInUse += ram;
		cpuInUse += cpu;
	}
		
	@Override
	public Float getPowerConsumtion() {
		return (cpuInUse - 500) * wToMhz + defaultPowerConsumption;
	}
	
	@Override
	public Integer getRunningVMCount() {
		return vms.size();
	}
	
	/**
	 * 2013-05-18, Alex Faustmann
	 */
	@Override
	public String toString(){
		StringBuffer buffer = new StringBuffer();
		
		buffer.append("{ vms : [");
		for (VirtualMachine vm : vms){
			buffer.append(vm.toString());
		}
		buffer.append("]}");
		
		return buffer.toString();
	}

	@Override
	public Collection<Application> getApplications() {
		ArrayList<Application> applications = new ArrayList<Application>();
		
		for (VirtualMachine vm : vms){
			applications.addAll(vm.getApplications());
		}
		
		return applications;
	}

	@Override
	public void increaseRunningTime(){
//		synchronized (this) {
//			List<VirtualMachine> modifiedVMs = new ArrayList<VirtualMachine>();
		for (VirtualMachine vm : new ArrayList<VirtualMachine>(vms)) {
			
		 vm.increaseRunningTime();
		 
//		 if(modifed){
//			 modifiedVMs.add(vm);
//		 }
		}
		
//		for (VirtualMachine vm : modifiedVMs) {
//			update((Observable) vm, null);
//		}
//		}
	}
}
